<body></body>

<script type="module">
	// 渲染器
	const renderer = createRenderer({
		createElement: (tag) => document.createElement(tag),
		setElementText: (el, text) => (el.textContent = text),
		insert: (el, parent, anchor = null) => parent.insertBefore(el, anchor),
		createText: (text) => document.createTextNode(text),
		createComment: (text) => document.createComment(text),
		setText: (el, text) => (el.nodeValue = text),

		patchProps: (el, key, prevValue, nextValue) => {
			// 特殊处理
			function shouldSetAsProps(el, key, value) {
				if (key === 'form' && el.tagName === 'INPUT') return false
				return key in el
			}
			if (key.startsWith('on')) {
				let invokers = el._vei || (el._vei = {}) // 定义一个el._vei 为一个对象，存在事件名称到事件处理函数的映射
				let invoker = invokers[key] // 根据事件名称获取 invoker
				const name = key.slice(2).toLowerCase() //获取元素事件

				if (nextValue) {
					if (!invoker) {
						// 时间处理函数缓存到 el._vei[key]下，避免覆盖
						invoker = el._vei[key] = (e) => {
							if (e.timeStamp < invoker.attached) return // e.timeStamp 是 事件发生时间,如果早于事件函数绑定时间不执行
							if (Array.isArray(invoker.value)) {
								invoker.value.forEach((fn) => fn(e)) // 如果 invoker.value 是数组，遍历他逐个调用事件处理函数
							} else {
								invoker.value(e)
							}
						}
						invoker.value = nextValue
						invoker.attached = performance.now() // 添加 invoker.attached 属性，存储事件处理函数被绑定的时间
						el.addEventListener(name, invoker)
					} else {
						invoker.value = nextValue
					}
				} else if (invoker) {
					el.removeEventListener(name, invoker) // 新的事绑定函数不存在，且之前绑定的 invoker 存在，则移除绑定
				}
			} else if (key === 'class') {
				// el.className = ''
			} else if (shouldSetAsProps(el, key, nextValue)) {
				const type = typeof el[key] //  获取该 DOM 属性值 的类型
				if (type === 'boolean' && nextValue === '') {
					el[key] = true // 如果是dom使用标准是布尔值，并且当前nextValue是空，将值矫正为true
				} else {
					el[key] = nextValue
				}
			} else {
				el.setAttribute(key, nextValue)
			}
		}
	})

	function createRenderer(optios) {
		const { createElement, setElementText, insert, createText, createComment, setText, patchProps } = optios

		// 挂载
		function mountElement(vnode, container) {
			const el = (vnode.el = createElement(vnode.type)) //  vnode.el 存 真实 DOM 元素 对的指针 ，用于回收元素

			if (typeof vnode.children === 'string') {
				// 元素是字符串，直接设置文本
				setElementText(el, vnode.children)
			} else if (Array.isArray(vnode.children)) {
				vnode.children.forEach((child) => {
					// 子节点是数组，遍历 child 调用 patch 挂载
					patch(null, child, el)
				})
			}

			if (vnode.props) {
				for (const key in vnode.props) {
					patchProps(el, key, null, vnode.props[key]) // 属性对比更新
				}
			}
			insert(el, container)
		}

		// 卸载操作
		function unmount(vnode) {
			if (vnode.type === Fragment) {
				return vnode.children.forEach((v) => unmount(v))
			}
			const el = vnode.el // 使用前面存旧的元素
			const parent = el.parentNode //获取父元素去移除元素
			if (parent) parent.removeChild(el)
		}

		// 渲染函数
		function render(vnode, container) {
			if (vnode) {
				patch(container._vnode, vnode, container) // 新vnode存在，将其与旧vnode一起传递给patch函数打补丁
			} else {
				if (container._vnode) {
					unmount(container._vnode) // newVnode不存在, 且 oldVnode存在 执行卸载(unmount)操作
				}
			}
			container._vnode = vnode // 保存节点
		}

		// 子节点更新：对比 字符串 | 数组 | null
		function patchChildren(oldVnode, newVnode, container) {
			if (typeof newVnode.children === 'string') {
				if (Array.isArray(oldVnode.children)) {
					oldVnode.children.forEach((c) => unmount(c)) // 只有当九子节点为一组的时候，才需要逐个卸载，其他情况下什么都不需要做
				}
				setElementText(container, newVnode.children) // 最后将新的文本节点内容设置给容器元素
			} else if (Array.isArray(newVnode.children)) {
				const oldChildren = oldVnode.children
				const newChildren = newVnode.children

				for (let i = 0; i < newChildren.length; i++) {
					for (let j = 0; j < oldChildren.length; j++) {
						// 如果找到了key相同，那么就是可以重复使用
						if (newChildren[i].key === oldChildren[j].key) {
							patch(oldChildren[j], newChildren[i], container)
							break
						}
					}
				}

				const oldLength = oldChildren.length
				const newLength = newChildren.length
				const commonLength = Math.min(oldLength, newLength)
				if (oldLength > newLength) {
					// 如果旧的节点更长，说明子节点需要卸载
					for (let i = 0; i < commonLength; i++) {
						unmount(oldChildren[i])
					}
				} else if (newLength > oldLength) {
					// 如果新的节点更长，说明子节点需要挂载
					for (let i = 0; i < commonLength; i++) {
						patch(null, newChildren[i], container)
					}
				}

				if (Array.isArray(oldVnode.children)) {
					for (let i = 0; i < commonLength; i++) {
						//  调用 patch 函数逐个更新子节点
						patch(oldChildren[i], newChildren[i])
					}
				} else {
					setElementText(container, '')
					newVnode.children.forEach((c) => patch(null, c, container))
				}
			} else {
				if (Array.isArray(oldVnode.children)) {
					oldVnode.children.forEach((v) => unmount(v)) // 就的子节点要么文本子节点，要么不存在，但无论哪种情况，我们都只需要将容器清空，然后加新的一组字节对数个挂载
				} else if (typeof oldVnode.children === 'string') {
					setElementText(container, '')
				}
			}
		}

		// 元素差异对比
		function patchElement(oldVnode, newVnode) {
			const el = (newVnode.el = oldVnode.el)
			const oldProps = oldVnode.props || {}
			const newProps = newVnode.props || {}

			// 更新属性 || 新增属性
			for (const key in newProps) {
				if (newProps[key] !== oldProps[key]) {
					patchProps(el, key, oldProps[key], newProps[key])
				}
			}
			for (const key in oldProps) {
				if (!key in newProps) {
					patchProps(el, key, oldProps[key], null)
				}
			}
			// 更新children
			patchChildren(oldVnode, newVnode, el)
		}

		// 补丁函数
		function patch(oldVnode, newVnode, container) {
			if (!newVnode && oldVnode) {
				//子节点覆盖
				unmount(oldVnode)
				return (oldVnode = null)
			}
			// 新旧两者类型不同
			if (oldVnode && oldVnode.type !== newVnode.type) {
				unmount(oldVnode)
				oldVnode = null
			}

			// 以下代码： oldVnodeType === newVnodeType
			const type = newVnode.type
			if (typeof type === 'string') {
				if (!oldVnode) {
					mountElement(newVnode, container)
				} else {
					patchElement(oldVnode, newVnode)
				}
			} else if (type === Text) {
				if (!oldVnode) {
					const el = (newVnode.el = createText(newVnode.children))
					insert(el, container)
				} else {
					const el = (newVnode.el = oldVnode.el)
					if (newVnode.children !== oldVnode.children) {
						setText(el, newVnode.children)
					}
				}
			} else if (type === Comment) {
				if (!oldVnode) {
					const el = (newVnode.el = createComment(newVnode.children))
					insert(el, container)
				} else {
					const el = (newVnode.el = oldVnode.el)
					if (newVnode.children !== oldVnode.children) {
						setText(el, newVnode.children)
					}
				}
			} else if (type === Fragment) {
				if (!oldVnode) {
					newVnode.children.forEach((v) => patch(null, v, container))
				} else {
					patchChildren(oldVnode, newVnode, container)
				}
			} else if (typeof type === 'object') {
				// 组件类型的Vnode
			} else if (type === 'xxx') {
				// 其他类型的Vnode
			}
		}

		return { render }
	}

	const Fragment = Symbol()
	const Text = Symbol()
	const oldVnode = {
		type: 'div',
		children: [
			{ type: 'p', children: '1', key: 1 },
			{ type: 'p', children: '2', key: 2 },
			{ type: 'p', children: '3', key: 3 }
		]
	}
	const newVnode = {
		type: 'div',
		children: [
			{ type: 'p', children: 'world', key: 3 },
			{ type: 'p', children: '1', key: 1 },
			{ type: 'p', children: '2', key: 2 }
		]
	}
	renderer.render(oldVnode, document.body)
	setTimeout(() => renderer.render(newVnode, document.body), 1000)
</script>
